package container

import (
	"log"
	"reflect"
	"strings"
	"sync"
)

const DEFAULT_CONTAINER_NAME = "_self_"

/**
 * containerImpl use map, default cap @see __defalut_container_size__
 * scope: privete
 */
type containerImpl struct {
	name                string
	instanceDefinitions map[string]*instanceDefinition
	mutex               sync.RWMutex
}

type instanceDefinition struct {
	name     string
	scope    SCOPE
	supplier Supplier
	instance interface{}
	mutex    sync.Mutex
}

/**
 * containerImpl* singleton instance, @see GetContainer
 * scope: privete
 */
var container Container = NewContainer(DEFAULT_CONTAINER_NAME)

func GetContainer() Container {
	return container.GetInstance(DEFAULT_CONTAINER_NAME).(Container)
}

func NewContainer(name string) Container {
	if len(strings.TrimSpace(name)) == 0 {
		log.Printf("Bad name : %s", name)
		return nil
	}
	newContainer := &containerImpl{
		instanceDefinitions: map[string]*instanceDefinition{},
		name:                name,
		mutex:               sync.RWMutex{},
	}
	if !newContainer.RegisterInstance(name, SINGLETON, func() interface{} {
		return newContainer
	}) {
		return nil
	}
	if c, ok := newContainer.GetInstance(name).(Container); ok {
		return c
	} else {
		return nil
	}
}

func (c *containerImpl) RegisterSingletonInstance(name string, supplier Supplier) bool {
	return c.RegisterInstance(name, SINGLETON, supplier)
}

func (c *containerImpl) RegisterInstance(name string, scope SCOPE, supplier Supplier) bool {

	if supplier == nil {
		log.Printf("param error. instance must be not null, name: %v", name)
		return false
	}
	if !scope.IsValid() {
		log.Printf("Invalid scope value, input value: %d - %s", scope, scope)
		return false
	}
	for _, n := range c.GetAllInstanceNames() {
		if n == name {
			log.Printf("param error. name : %v is exist", name)
			return false
		}
	}
	if nil == c.instanceDefinitions[name] {
		c.mutex.Lock()
		if nil == c.instanceDefinitions[name] {
			c.instanceDefinitions[name] = &instanceDefinition{
				name:     name,
				scope:    scope,
				supplier: supplier,
				mutex:    sync.Mutex{},
			}
		}
		c.mutex.Unlock()
	}
	return true
}

func (c *containerImpl) UnRegisterInstance(name string) {
	c.mutex.Lock()
	delete(c.instanceDefinitions, name)
	c.mutex.Unlock()
}

func (c *containerImpl) GetInstance(name string) interface{} {
	c.mutex.RLock()
	defer c.mutex.RUnlock()
	instanceDef := c.instanceDefinitions[name]
	if nil != instanceDef && nil != instanceDef.supplier {
		switch instanceDef.scope {
		case PROTOTYPE:
			return c.assemble(instanceDef.supplier)
		case SINGLETON:
			if nil == instanceDef.instance {
				instanceDef.mutex.Lock()
				if nil == instanceDef.instance {
					instanceDef.instance = c.assemble(instanceDef.supplier)
				}
				instanceDef.mutex.Unlock()
			}
			return instanceDef.instance
		default:
			return nil
		}
	}
	return nil
}

func (c *containerImpl) GetAllInstanceNames() []string {
	c.mutex.RLock()
	keys := make([]string, len(c.instanceDefinitions))
	for k := range c.instanceDefinitions {
		keys = append(keys, k)
	}
	c.mutex.RUnlock()
	return keys
}

func (c *containerImpl) GetContainerName() string {
	return c.name
}

// interface Container implement end.

func (c *containerImpl) assemble(supplier Supplier) interface{} {
	if nil == supplier {
		log.Println("supplier and container must be not nil")
		return nil
	}
	inst := supplier()
	if nil == inst {
		return nil
	}
	instTyp, instVal := reflect.TypeOf(inst), reflect.ValueOf(inst)
	isPtr := false
	if instTyp.Kind() != reflect.Ptr {
		val := reflect.New(instTyp)
		val.Elem().Set(instVal)
		instVal = val.Elem()
	} else {
		isPtr = true
		instTyp, instVal = instTyp.Elem(), instVal.Elem()
	}

	switch instTyp.Kind() {
	case reflect.Struct:
		for i := 0; i < instTyp.NumField(); i++ {
			field, fieldVal := instTyp.Field(i), instVal.Field(i)
			tag := field.Tag.Get(FRAMEWORK_FIELD_TAG_PREFIX)
			if !fieldVal.CanSet() || len(tag) == 0 {
				continue
			}

			dependentInst := c.GetInstance(tag)
			if dependentInst == nil {
				continue
			}
			inject(fieldVal, dependentInst)
		}
	default:
		// inject(instVal, dependentInst) will support more type
		break
	}
	if isPtr {
		if instVal.CanAddr() {
			instVal = instVal.Addr()
		} else {
			return nil
		}
	}

	if instVal.CanInterface() {
		return instVal.Interface()
	}

	return nil
}

func inject(instVal reflect.Value, dependentInst interface{}) {
	isPtr := false
	instTyp := instVal.Type()
	if instTyp.Kind() == reflect.Ptr {
		isPtr = true
		instTyp = instTyp.Elem()
		if instVal.IsNil() {
			instVal.Set(reflect.New(instTyp))
		}
	}
	if val, succ := getValue(instTyp, dependentInst); succ {
		if isPtr {
			instVal.Elem().Set(val)
		} else {
			instVal.Set(val)
		}
	}
}

func getValue(fieldTyp reflect.Type, dependentInst interface{}) (reflect.Value, bool) {

	dependentTyp, dependentVal := reflect.TypeOf(dependentInst), reflect.ValueOf(dependentInst)

	if fieldTyp.Kind() != reflect.Interface && dependentTyp.Kind() == reflect.Ptr {
		dependentTyp = dependentTyp.Elem()
		dependentVal = dependentVal.Elem()
	}

	if fieldTyp.Kind() == reflect.Interface && dependentTyp.Kind() != reflect.Ptr && dependentTyp.Kind() != reflect.Interface {
		val := reflect.New(dependentTyp)
		val.Elem().Set(dependentVal)
		dependentTyp = val.Type()
		dependentVal = val
	}

	if dependentTyp.AssignableTo(fieldTyp) {
		return dependentVal, true
	}

	if dependentTyp.ConvertibleTo(fieldTyp) {
		return dependentVal.Convert(fieldTyp), true
	}

	return reflect.Value{}, false
}
